Effective OC 46:不要用dispatch_current_queue

结论

国际惯例先说结论:dispatch_current_queue已经被废弃了,虽然可以继续使用,但是会出问题的,比如说,你以为dispatch_current_queue返回的是queueA,但是queueA是在queueB的block中执行,外层的queueB会死锁。

原因

第一点是会造成简单的死锁,

如下代码:

dispatch_sync(queueA,^{
    dispatch_sync(queueB, ^{
        dispatch_block_t block = ^{/*...*/};
        if(dispatch_get_current_queue() == queueA) {
            block();
        }else {
            dispatch_sync(queueA,block);
        }
    })
});

这段代码是有问题的,代码的原意是想要在queueA上执行这个block,但是一开始dispatch_sync的queueA又在等待block执行完,造成死锁。这种写法略装b,是为了死锁而死锁的写法,但是还有一种意象不到的情况会死锁(虽然见的也不多)。

第二点,就是结论中说的queueA是在queueB的block中执行,外层的queueB会死锁。

队列是有层级体系的,以effective oc里的举例来看(p182图6-4),队列B,C的target是A,那么B,C里的block会在队列A中串行执行,队列A,D的target是全局的并发队列,那么队列A,D里的block会并发执行,如果有多个核心可能还会开多个线程并行执行。队列的层级体系可以通过dispatch_set_target_queue来设置。

dispatch_set_target_queue官方文档的说法是:

A dispatch queue’s priority is inherited from its target queue. Use the dispatch_get_global_queue function to obtain a suitable target queue of the desired priority.

If you submit a block to a serial queue, and the serial queue’s target queue is a different serial queue, that block is not invoked concurrently with blocks submitted to the target queue or to any other queue with that same target queue.

Important

If you modify the target queue for a queue, you must be careful to avoid creating cycles in the queue hierarchy.

简单翻译过来就是:

disptach_set_target_queue两个作用:

  1. 队列(queue)的优先级是继承自他的目标队列(target queue)
  2. 如果提交一个block到串行队列,这个串行队列的目标队列是另一个串行队列,这个block不会和目标队列里的其他block并发执行,而是按照提交顺序执行。

文档里的说法其实和上文说的一样,这样的话,一个blockC如果是在队列c中执行,但是队列c又是在队列A的blockA中执行,这时blockC中get_current_queue拿到的是queueC,然后放心的去queueA中执行其它操作,但是此时queueA其实正在等待自己的blockA执行完成,而blockA又在等queueC的blockC执行完成,这个时候在queueA中执行其它操作就容易造成死锁。

最后,通过effective oc上给的例子来试验一下dispatch_set_target_queue和dispatch_get_specific。

dispatch_queue_t queueA = dispatch_queue_create("bifangao.queueA", NULL);
dispatch_queue_t queueB = dispatch_queue_create("bifangao.queueB", NULL);
dispatch_set_target_queue(queueB, queueA);

static int kQueueSpecific;
CFStringRef queueSpecificValue = CFSTR("queueA");
dispatch_queue_set_specific(queueA, &kQueueSpecific, (void*)queueSpecificValue,     (dispatch_function_t)CFRelease);


dispatch_sync(queueB, ^{
    dispatch_block_t block = ^{
        NSLog(@"no deadlock");
    };
    CFStringRef retrievedValue = dispatch_get_specific(&kQueueSpecific);
    if (retrievedValue) {
        block();
    }else{
        dispatch_sync(queueA, block);
    }
});

如果把第三行的dispatch_set_target注释掉,在下面的dispatch_sync中会拿不到retrievedValue走else里的语句,在queueA中执行block,但是如果set了target,是可以拿到retrievedValue,在queueB中执行block的,原因在于如果没有dispatch_set_target,当前代码在哪个队列中执行,就拿到哪个队列的specific,但是如果设置了目标队列,则可以顺着队列链找到queueA,继而拿到queueA的specific,最后在queueB中执行block。
原文:

The misunderstanding here is that dispatch_get_specific doesn’t traverse the stack of nested queues, it traverses the queue targeting lineage. For instance, if you did this instead,

出处:StackOverflow